﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Net.Sockets;

namespace Pixelator
{
    public partial class MainWindow : Form
    {
        #region consts

        const int PACKET_SIZE = 8;
        const int COLORS_Q = 3;
        const int BYTE_SIZE = 8;
        const int rowsInit = 16;
        const int columnsInit = 16;
        const int squareWidthInit = 20;
        const int rowsMax = 64;
        const int mouseDownTimerInterval = 500;

        #endregion

        #region variables

        Boolean mouseDownLeft = false;
        Boolean mouseDownRight = false;
        Timer mouseDownTimer = new Timer();

        #endregion

        public MainWindow()
        {
            InitializeComponent();
            initializeGrid();
            initializeComboPorts();
            
            //Inicializo el timer
            mouseDownTimer.Tick += new EventHandler(mouseDownTimer_Tick);
            mouseDownTimer.Interval = mouseDownTimerInterval;       

            //Inicializo el combo de rows
            nudRows.Value = columnsInit;
            nudRows.Maximum = rowsMax;
        }

        void initializeGrid()
        {
            int i, j;

            for (i = 0; i < columnsInit; i++)
            {
                gridMatrix.Columns.Add("col" + i, null);
                gridMatrix.Columns[i].Width = squareWidthInit;
            }

            for (i = 0; i < rowsMax; i++)
            {
                gridMatrix.Rows.Add();
                gridMatrix.Rows[i].Height = squareWidthInit;
            }

            for (i = 0; i < rowsMax; i++)
            {
                for (j = 0; j < columnsInit; j++)
                {
                    gridMatrix.Rows[i].Cells[j].Value = new Led();
                    gridMatrix.Rows[i].Cells[j].Style.BackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                    gridMatrix.Rows[i].Cells[j].Style.SelectionBackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                }
            }

            gridMatrix.CellMouseDown += new DataGridViewCellMouseEventHandler(gridMatrix_CellMouseDown);
            gridMatrix.CellMouseEnter += new DataGridViewCellEventHandler(gridMatrix_CellMouseEnter);
            gridMatrix.CellMouseUp += new DataGridViewCellMouseEventHandler(gridMatrix_CellMouseUp);
            gridMatrix.CellMouseLeave += new DataGridViewCellEventHandler(gridMatrix_CellMouseLeave);
            gridMatrix.DefaultCellStyle.SelectionForeColor = Color.Transparent;
            gridMatrix.DefaultCellStyle.ForeColor = Color.Transparent;
            gridMatrix.DefaultCellStyle.BackColor = Color.DarkGray;
            gridMatrix.Size = new Size((columnsInit * squareWidthInit) + 15, (rowsInit * squareWidthInit) + 3);
            gridMatrix.BorderStyle = BorderStyle.None;
        }

        void initializeComboPorts()
        {
            //Agrego los puertos serie
            string[] theSerialPortNames = System.IO.Ports.SerialPort.GetPortNames();
            foreach (String port in theSerialPortNames)
            {
                cbPort.Items.Add(new KeyValuePair<Object, String>("serial", port));
            }
            cbPort.DisplayMember = "value";
            cbPort.ValueMember = "key";
        }

        private void cbPort_SelectedIndexChanged(object sender, EventArgs e)
        {
            serialPort.PortName = ((KeyValuePair<Object,String>)cbPort.SelectedItem).Value;
        }

        #region MyConnectionMethods

        void MyConnectionOpen()
        {
            serialPort.Open();
            serialPort.DiscardOutBuffer();
        }

        void MyConnectionSend(byte[] bufferToSend)
        {

            for (int i = 0; i < bufferToSend.Length; i++)
            {
                try
                {
                    serialPort.Write(bufferToSend, i, 1);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: " + ex.Message);
                    return;
                }
                pbSending.PerformStep();
                System.Threading.Thread.Sleep(10);
            }
        }

        void MyConnectionClose()
        {

            //Cierro serial
            serialPort.Close();
        }

        #endregion

        private void btnSendImage_Click(object sender, EventArgs e)
        {
            int dim, acum, i, j, k, p;

            int ROWS_Q = (int)nudRows.Value;
            int LEDS_Q = columnsInit;
            int LEDS_SIZE = LEDS_Q / BYTE_SIZE;
            int[] powersOf2 = new int[8] { 128, 64, 32, 16, 8, 4, 2, 1 };

            if (cbPort.SelectedIndex == -1)
            {
                MessageBox.Show("Error: " + "No port selected");
                return;
            }

            //Antes que nada reseteo las rowsQ
            sendParameter(operationType.SET_ROWSQ, (byte)ROWS_Q);

            byte[][][] intImage = matrixToIntImage();

            byte[][][] my_image = new byte[COLORS_Q][][];
            for (i = 0; i < COLORS_Q; i++)
            {
                my_image[i] = new byte[ROWS_Q][];
                for (j = 0; j < ROWS_Q; j++)
                {
                    my_image[i][j] = new byte[LEDS_Q / BYTE_SIZE];
                }
            }

            byte[] plainImage = new byte[COLORS_Q * ROWS_Q * LEDS_SIZE];
            int plainImageIndex = 0;

            for (k = 0; k < COLORS_Q; k++)
            {
                for (i = 0; i < ROWS_Q; i++)
                {
                    dim = 0;
                    for (j = 0; j < LEDS_SIZE; j++)
                    {
                        acum = 0;
                        for (p = 0; p < BYTE_SIZE; p++, dim++)
                        {
                            acum += (intImage[k][i][dim] * powersOf2[p]);
                        }
                        my_image[k][i][j] = byte.Parse(acum.ToString());
                        plainImage[plainImageIndex++] = my_image[k][i][j];
                    }
                }
            }

            MyConnectionOpen();

            pbSending.Step = (Int32)Math.Ceiling(100.0 / (COLORS_Q * ROWS_Q * LEDS_SIZE * PACKET_SIZE));
            lblSending.Visible = true;
            this.Enabled = false;

            byte[] bufferToSend = new byte[PACKET_SIZE];

            for (k = 0; k < plainImage.Length; )
            {
                if ( ( k + PACKET_SIZE - 2 ) == plainImage.Length )
                    bufferToSend[0] = (byte)operationType.END_DATA;
                else
                    bufferToSend[0] = (byte)operationType.STATIC_IMAGE;
                for (i = 1; i < PACKET_SIZE - 1 && k < plainImage.Length; i++)
                {
                    bufferToSend[i] = plainImage[k++];
                }
                //Checksum
                bufferToSend[PACKET_SIZE - 1] = 0x14;
                MyConnectionSend(bufferToSend);
            }

            pbSending.Value = 0;
            lblSending.Visible = false;
            this.Enabled = true;

            MyConnectionClose();
        }

        private void btnSendTimerCounter_Click(object sender, EventArgs e)
        {
            if (cbPort.SelectedIndex == -1)
            {
                MessageBox.Show("Error: " + "No port selected");
                return;
            }
            sendParameter(operationType.SET_TIMERCOUNTER, byte.Parse(nudTimerCounter.Value.ToString()));
        }

        private void btnResetImage_Click(object sender, EventArgs e)
        {
            int i, j;
            for (i = 0; i < rowsMax; i++)
            {
                for (j = 0; j < columnsInit; j++)
                {   
                    (gridMatrix.Rows[i].Cells[j].Value as Led).reset();
                    gridMatrix.Rows[i].Cells[j].Style.BackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                    gridMatrix.Rows[i].Cells[j].Style.SelectionBackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                }
            }
        }

        private void btnSaveImage_Click(object sender, EventArgs e)
        {
            saveFileDialog.ShowDialog(this);
            System.IO.StreamWriter file = new System.IO.StreamWriter(saveFileDialog.FileName);
            int i,j,k;
            byte[][][] intImage = matrixToIntImage();

            for (k = 0; k < COLORS_Q; k++)
            {
                for (i = 0; i < nudRows.Value; i++)
                {
                    for (j = 0; j < columnsInit; j++)
                    {
                        file.Write( intImage[k][i][j] );
                    }
                    file.Write("\r\n");
                }
                file.Write("\r\n\r\n");
            }

            file.Flush();
            file.Close();
        }

        private void btnLoadImage_Click(object sender, EventArgs e)
        {
            openFileDialog.ShowDialog(this);
            System.IO.StreamReader file = new System.IO.StreamReader(openFileDialog.FileName);
            int i, j=0, k;
            String pivot;
            byte[][][] intImage = new byte[3][][];

            for (i = 0; i < 3; i++)
            {
                intImage[i] = new byte[rowsMax][];
                for (j = 0; j < rowsMax; j++)
                {
                    intImage[i][j] = new byte[columnsInit];
                }
            }

            for (k = 0; !file.EndOfStream; k++ )
            {
                for (i = 0; (pivot = file.ReadLine()) != "";i++ )
                {
                    for (j = 0; j < pivot.Length; j++)
                    {
                        (intImage[k][i][j]) = byte.Parse(pivot[j].ToString());
                    }
                }
                file.ReadLine();
            }
            file.Close();

            nudRows.Value = i;

            btnResetImage_Click(this, new EventArgs());
            IntImageToMatrix(intImage);
        }

        private byte[][][] matrixToIntImage()
        {
            int ROWS_Q = (int)nudRows.Value;
            int LEDS_Q = columnsInit;
            int LEDS_SIZE = LEDS_Q / BYTE_SIZE;
            int i, j, k;

            byte[][][] intImage = new byte[COLORS_Q][][];
            for (i = 0; i < COLORS_Q; i++)
            {
                intImage[i] = new byte[ROWS_Q][];
                for (j = 0; j < ROWS_Q; j++)
                {
                    intImage[i][j] = new byte[LEDS_Q];
                }
            }

            //Parseo el gridView a intImage (la imagen de COLORS*LEDS*ROWS)
            for (k = 0; k < COLORS_Q; k++)
            {
                for (i = 0; i < nudRows.Value; i++)
                {
                    for (j = 0; j < columnsInit; j++)
                    {
                        intImage[k][i][j] = (gridMatrix.Rows[i].Cells[j].Value as Led).hasColor(k);
                    }
                }
            }

            return intImage;
        }

        private void IntImageToMatrix(byte[][][] intImage)
        {
            int ROWS_Q = (int)nudRows.Value;
            int LEDS_Q = columnsInit;
            int LEDS_SIZE = LEDS_Q / BYTE_SIZE;
            int i, j, k;

            for (k = 0; k < COLORS_Q; k++)
            {
                for (i = 0; i < nudRows.Value; i++)
                {
                    gridMatrix.Rows[i].Visible = true;
                    for (j = 0; j < columnsInit; j++)
                    {
                        gridMatrix.Columns[j].Visible = true;
                        if (intImage[k][i][j] == 1)
                            (gridMatrix.Rows[i].Cells[j].Value as Led).addColor(k);

                        gridMatrix.Rows[i].Cells[j].Style.BackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                        gridMatrix.Rows[i].Cells[j].Style.SelectionBackColor = (gridMatrix.Rows[i].Cells[j].Value as Led).currentColor();
                    }
                }
            }

        }

        private void sendParameter(operationType operation, byte parameter)
        {
            byte[] bufferToSend = new byte[PACKET_SIZE];
            bufferToSend[0] = (byte)operation;
            bufferToSend[1] = parameter;
            pbSending.Step = 100 / PACKET_SIZE;
            lblSending.Visible = true;
            this.Enabled = false;

            MyConnectionOpen();
            MyConnectionSend(bufferToSend);
            MyConnectionClose();

            pbSending.Value = 0;
            lblSending.Visible = false;
            this.Enabled = true;
        }

        #region events

        void mouseDownTimer_Tick(object sender, EventArgs e)
        {
            gridMatrix.CurrentCell.Style.BackColor = (gridMatrix.CurrentCell.Value as Led).nextColor();
            gridMatrix.CurrentCell.Style.SelectionBackColor = (gridMatrix.CurrentCell.Value as Led).currentColor();

        }

        void gridMatrix_CellMouseLeave(object sender, DataGridViewCellEventArgs e)
        {
            mouseDownTimer.Stop();
        }

        void gridMatrix_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
        {
            mouseDownLeft = false;
            mouseDownRight = false;
            mouseDownTimer.Stop();
        }

        void gridMatrix_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
        {
            if (mouseDownLeft)
            {
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).nextColor();
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.SelectionBackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
                mouseDownTimer.Start();
                return;
            }

            if (mouseDownRight)
            {
                (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).reset();
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.SelectionBackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
                return;
            }
        }

        void gridMatrix_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).reset();
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
                gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.SelectionBackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
                mouseDownRight = true;
                return;
            }

            gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).nextColor();
            gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.SelectionBackColor = (gridMatrix.Rows[e.RowIndex].Cells[e.ColumnIndex].Value as Led).currentColor();
            mouseDownLeft = true;
            mouseDownTimer.Start();
        }

        private void nudRows_ValueChanged(object sender, EventArgs e)
        {
            for (int i = 0; i < rowsMax; i++)
            {
                gridMatrix.Rows[i].Visible = (i < nudRows.Value);
            }
        }

        #endregion
    }

    #region LedClass

    public class Led : Object
    {
        static int ledColorsSize = 8;
        static Color[] ledColors = new Color[8] { Color.DarkGray, Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Magenta, Color.Cyan, Color.White };
        private int indexColor { get; set; }

        public Led()
        {
            indexColor = 0;
        }

        public Color nextColor()
        {
            indexColor++;
            if (indexColor >= ledColorsSize)
                indexColor = 0;

            return currentColor();
        }

        public Color currentColor()
        {
            return ledColors[indexColor];
        }

        public byte hasColor(int k)
        {
            Color currColor = currentColor();

            //R
            if (k == 0)
                return (byte)((currColor == Color.Red | currColor == Color.Yellow | currColor == Color.White | currColor == Color.Magenta) ? 1 : 0);
            //G
            if (k == 1)
                return (byte)((currColor == Color.Green | currColor == Color.Yellow | currColor == Color.White | currColor == Color.Cyan)?1:0);
            //B
            if (k == 2)
                return (byte)((currColor == Color.Blue | currColor == Color.Cyan | currColor == Color.White | currColor == Color.Magenta) ? 1 : 0);

            return 2;
        }

        public void addColor(int k)
        {
            Color newColor;
            int r;
            int g;
            int b;

            if (indexColor == 0)
            {
                r = 0;
                g = 0;
                b = 0;
            }
            else
            {
                r = currentColor().R;
                g = currentColor().G;
                b = currentColor().B;
            }

            //R
            if (k == 0 && r == 0)
                r = 255;
            //G
            if (k == 1 && g == 0)
                g = 255;
            //B
            if (k == 2 && b == 0)
                b = 255;

            newColor = MyFromRGB(r, g, b);

            for (int i = 1; i < ledColors.Length; i++)
            {
                if (ledColors[i] == newColor)
                    indexColor = i;
            }
        }

        Color MyFromRGB(int r, int g, int b)
        {
            if (r != 0 && g == 0 && b == 0)
                return Color.Red;
            if (r == 0 && g != 0 && b == 0)
                return Color.Green;
            if (r == 0 && g == 0 && b != 0)
                return Color.Blue;
            if (r != 0 && g != 0 && b == 0)
                return Color.Yellow;
            if (r != 0 && g == 0 && b != 0)
                return Color.Magenta;
            if (r == 0 && g != 0 && b != 0)
                return Color.Cyan;
            if (r != 0 && g != 0 && b != 0)
                return Color.White;

            return Color.DarkGray;
            
        }

        public void reset()
        {
            indexColor = 0;
        }

        public override string ToString()
        {
            return "";
        }
    }

    #endregion

    #region enums

    public enum operationType { END_DATA = 1, STATIC_IMAGE = 2, SET_TIMERCOUNTER = 3, SET_ROWSQ = 4 };
    
    #endregion
}